#!/usr/bin/env python3
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This software may be used and distributed according to the terms of the
# GNU General Public License version 2.

import argparse
import asyncio
import shutil
import time
from argparse import RawTextHelpFormatter
from pathlib import Path
from typing import List

addons = Path(__file__).parent


def main():
    parser = argparse.ArgumentParser(
        description="""\
Verifies the contents of this folder by running tests and linters.

Requirements:
- `node` and `yarn` are on the `$PATH`
- `yarn install` has already been run in the addons/ folder
""",
        formatter_class=RawTextHelpFormatter,
    )
    parser.add_argument(
        "--use-vendored-grammars",
        help=(
            "Skips the codegen step for TextMate grammars that "
            + "fetches content from raw.githubusercontent.com. "
            + "Assumes TextMate codegen is already available."
        ),
        action="store_true",
    )
    parser.add_argument(
        "--no-vendored-grammars",
        help=("Reverse of --use-vendored-grammars (default)."),
        action="store_true",
    )
    args = parser.parse_args()
    if args.use_vendored_grammars:
        use_vendored_grammars = True
    elif args.no_vendored_grammars:
        use_vendored_grammars = False
    else:
        use_vendored_grammars = True
    asyncio.run(verify(use_vendored_grammars=use_vendored_grammars))


async def verify(*, use_vendored_grammars=True):
    await asyncio.gather(
        verify_prettier(),
        verify_shared(),
        verify_textmate(),
    )
    # shared depends on reviewstack generated textmate grammars, so can't run concurrently without flakiness
    await verify_reviewstack(use_vendored_grammars=use_vendored_grammars)


async def verify_prettier():
    timer = Timer("verifying prettier")
    await run(["yarn", "run", "prettier-check"], cwd=addons)
    timer.report(ok("prettier"))


async def verify_shared():
    timer = Timer("verifying shared/")
    shared = addons / "shared"
    await lint_and_test(shared)
    timer.report(ok("shared/"))


async def verify_textmate():
    timer = Timer("verifying textmate/")
    textmate = addons / "textmate"
    await asyncio.gather(
        run(["yarn", "run", "tsc", "--noEmit"], cwd=textmate),
        run(["yarn", "run", "eslint"], cwd=textmate),
    )
    timer.report(ok("textmate/"))


async def verify_reviewstack(*, use_vendored_grammars=False):
    timer = Timer("verifying reviewstack/")
    cwd = addons / "reviewstack"
    if use_vendored_grammars:
        # Normally, the full codegen step takes care of copying onig.wasm.
        src_onig_wasm = (
            addons / "node_modules" / "vscode-oniguruma" / "release" / "onig.wasm"
        )
        dest_onig_wasm = (
            addons
            / "reviewstack.dev"
            / "public"
            / "generated"
            / "textmate"
            / "onig.wasm"
        )
        shutil.copyfile(src_onig_wasm, dest_onig_wasm)
        await run(["yarn", "graphql"], cwd=cwd)
    else:
        await run(["yarn", "codegen"], cwd=cwd)
    await asyncio.gather(lint_and_test(cwd), verify_reviewstack_dev())
    timer.report(ok("reviewstack/"))


async def lint_and_test(cwd: Path):
    await asyncio.gather(
        run(["yarn", "run", "eslint"], cwd=cwd),
        run(["yarn", "test", "--watchAll=false"], cwd=cwd),
    )


async def verify_reviewstack_dev():
    """Requires codegen from reviewstack/ to have been built."""
    timer = Timer("verifying reviewstack.dev/")
    cwd = addons / "reviewstack.dev"
    await run(["yarn", "build"], cwd=cwd)
    await run(["yarn", "run", "eslint"], cwd=cwd)
    await run(["yarn", "release"], cwd=cwd)
    timer.report(ok("reviewstack.dev/"))


async def run(args: List[str], cwd: str):
    process = await asyncio.create_subprocess_exec(
        *args, cwd=cwd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE
    )
    stdout, stderr = await process.communicate()
    if process.returncode != 0:
        print(f"[stdout]\n{stdout.decode()}")
        print(f"[stderr]\n{stderr.decode()}")
        raise RuntimeError(f"command failed: {' '.join(args)}")


def ok(message: str) -> str:
    return f"\033[0;32mOK\033[00m {message}"


class Timer:
    def __init__(self, message):
        self._start = time.time()
        print(message)

    def report(self, message: str):
        end = time.time()
        duration = end - self._start
        print(f"{message} in {duration:.2f}s")


main()
